package com.google.code.proto.gcless;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

public class MemlessGenerator {

	private static final String HEADER = "// Generated by the protocol buffer gcless compiler.  DO NOT EDIT!\n";

	public static void main(String[] args) {
		if (args.length < 2) {
			System.out.println("invalid usage. Expected: <output-path> <proto-files>");
			return;
		}

		File output = new File(args[0]);
		if (!output.exists()) {
			System.out.println("<output-path> " + output.getAbsolutePath() + " doesnt exist");
			return;
		}
		if (!output.isDirectory()) {
			System.out.println("<output-path> " + output.getAbsolutePath() + " is not a directory");
			return;
		}

		for (int i = 1; i < args.length; i++) {
			try {
				process(output, args[i]);
			} catch (Exception e) {
				System.out.println("unable to process: " + args[i]);
				e.printStackTrace();
			}
		}
	}

	private static void process(File output, String filename) throws Exception {
		MemlessParser parser = new MemlessParser();
		parser.process(filename);
		process(output, parser);
		for (MemlessParser cur : parser.getImportedParsers()) {
			process(output, cur);
		}
	}

	private static void process(File output, MemlessParser parser) throws IOException, Exception {
		String packageName = parser.getPackageName();
		if (packageName != null) {
			output = createPackage(output, packageName);
		}

		BufferedWriter w = null;
		if (parser.getOuterClassName() != null) {
			w = new BufferedWriter(new FileWriter(new File(output, parser.getOuterClassName() + ".java")));
			w.append(HEADER);
			appendPackage(w, parser.getPackageName());
			w.append("public final class ");
			w.append(parser.getOuterClassName());
			w.append(" {\nprivate ");
			w.append(parser.getOuterClassName());
			w.append("() {}\n");
		}

		for (ProtobufEnum curEnum : parser.getEnums()) {
			String curEnumData = generateEnum(curEnum);
			if (parser.getOuterClassName() != null && w != null) {
				w.append(curEnumData);
			} else {
				BufferedWriter enumWriter = new BufferedWriter(new FileWriter(new File(output, curEnum.getName() + ".java")));
				appendPackage(enumWriter, parser.getPackageName());
				enumWriter.append(curEnumData);
				enumWriter.flush();
				enumWriter.close();
			}
		}

		GeneratorConfiguration config = new GeneratorConfiguration(System.getProperties());

		for (ProtobufMessage curMessage : parser.getMessages()) {
			String curMessageData = generateMessage(curMessage, parser.getOuterClassName(), config);
			String serializerData = generateSerializer(curMessage, parser.getOuterClassName(), config);

			if (parser.getOuterClassName() != null && w != null) {
				w.append(curMessageData);
				w.append(serializerData);
			} else {
				BufferedWriter messageWriter = new BufferedWriter(new FileWriter(new File(output, curMessage.getName() + ".java")));
				appendPackage(messageWriter, parser.getPackageName());
				messageWriter.append(curMessageData);
				messageWriter.flush();
				messageWriter.close();
				messageWriter = new BufferedWriter(new FileWriter(new File(output, curMessage.getName() + "Serializer" + ".java")));
				appendPackage(messageWriter, parser.getPackageName());
				messageWriter.append(serializerData);
				messageWriter.flush();
				messageWriter.close();
			}
		}

		if (parser.getOuterClassName() != null && w != null) {
			w.append("}\n\n");
			w.flush();
			w.close();
		}

		if (config.isInterfaceBased()) {
			String generateDefaultImpl = System.getProperty("generate.default");
			if (generateDefaultImpl != null && generateDefaultImpl.equals("true")) {
				for (ProtobufMessage curMessage : parser.getMessages()) {
					generateDefaultMessageImpl(curMessage, output, parser.getPackageName());
				}
			}
		}
		copy("ProtobufOutputStream.java", output, parser.getPackageName());
		copy("ProtobufInputStream.java", output, parser.getPackageName());
		copy("CurrentCursor.java", output, parser.getPackageName());
		if (config.isInterfaceBased()) {
			copy("MessageFactory.java", output, parser.getPackageName());
		}
	}

	private static void copy(String classpathFileName, File output, String packageName) throws IOException {
		InputStream protobufOutputStream = MemlessGenerator.class.getClassLoader().getResourceAsStream(classpathFileName);
		if (protobufOutputStream == null) {
			throw new IOException("Cannot find " + classpathFileName + " in classpath");
		}
		FileOutputStream fos = null;
		try {
			fos = new FileOutputStream(new File(output, classpathFileName));
			copy(protobufOutputStream, fos, packageName);
		} finally {
			protobufOutputStream.close();
			if (fos != null) {
				fos.close();
			}
		}
	}

	private static void copy(InputStream input, OutputStream output, String packageName) throws IOException {
		String curLine = null;
		BufferedReader r = new BufferedReader(new InputStreamReader(input));
		BufferedWriter w = new BufferedWriter(new OutputStreamWriter(output));
		while ((curLine = r.readLine()) != null) {
			String trimmedLine = curLine.trim();
			if (trimmedLine.startsWith("package ")) {
				curLine = "package " + packageName + ";\n";
			}
			w.append(curLine);
			w.append("\n");
		}
		w.flush();
	}

	private static String generateSerializer(ProtobufMessage curMessage, String outerClassName, GeneratorConfiguration config) {
		StringBuilder result = new StringBuilder();
		String fullMessageType = curMessage.getFullyClarifiedJavaName();
		if (outerClassName == null) {
			result.append("public final class ");
		} else {
			result.append("public static class ");
		}
		result.append(curMessage.getName());
		result.append("Serializer {\n");
		createSerializeToBytes(curMessage, result, fullMessageType);
		createSerializeToStream(curMessage, result, fullMessageType);
		createParseFromBytes(config.isInterfaceBased(), result, fullMessageType);
		createParseFromBytesWithLimit(config.isInterfaceBased(), result, fullMessageType);
		createParseFromBytesCursor(curMessage, config.isInterfaceBased(), result, fullMessageType);
		createParseFromStream(config.isInterfaceBased(), result, fullMessageType);
		createParseFromStreamWithLength(config.isInterfaceBased(), result, fullMessageType);
		createParseFromStreamCursor(curMessage, config.isInterfaceBased(), result, fullMessageType);
		if (hasRequired(curMessage)) {
			createAssertInitialized(curMessage, result, fullMessageType);
		}
		result.append("}\n");
		return result.toString();
	}

	private static void createSerializeToBytes(ProtobufMessage curMessage, StringBuilder result, String fullMessageType) {
		result.append("public static byte[] serialize(" + fullMessageType + " message) {\n");
		if (curMessage.getFields().isEmpty()) {
			result.append("return new byte[0];\n");
		} else {
			result.append("try {\n");
			if (hasRequired(curMessage)) {
				result.append("assertInitialized(message);\n");
			}
			result.append("int totalSize = 0;\n");
			for (ProtobufField curField : curMessage.getFields()) {
				if (curField.isEnumType()) {
					if (curField.getNature().equals("repeated")) {
						result.append("byte[] " + curField.getName() + "Buffer = null;\n");
						result.append("if (message.has" + curField.getBeanName() + "()) {\n");
						result.append("java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();\n");
						result.append("for( int i=0;i<message.get" + curField.getBeanName() + "().size();i++) {\n");
						result.append("ProtobufOutputStream.writeEnum(" + curField.getTag() + ", message.get" + curField.getBeanName() + "().get(i).getValue(), baos);\n");
						result.append("}\n");
						result.append(curField.getName() + "Buffer = baos.toByteArray();\n");
						result.append("totalSize += " + curField.getName() + "Buffer.length;\n");
					} else {
						result.append("if (message.has" + curField.getBeanName() + "()) {\n");
						result.append("totalSize += ProtobufOutputStream.computeEnumSize(" + curField.getTag() + ", message.get" + curField.getBeanName() + "().getValue());\n");
					}
					result.append("}\n");
					continue;
				}
				if (curField.isComplexType()) {
					result.append("byte[] " + curField.getName() + "Buffer = null;\n");
					result.append("if (message.has" + curField.getBeanName() + "()) {\n");
					if (curField.getNature().equals("repeated")) {
						result.append("java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();\n");
						result.append("for( int i=0;i<message.get" + curField.getBeanName() + "().size();i++) {\n");
						result.append("byte[] curMessageData = " + curField.getFullyClarifiedJavaType() + "Serializer.serialize(message.get" + curField.getBeanName() + "().get(i));\n");
						if (curField.isGroup()) {
							result.append("ProtobufOutputStream.writeTag(" + curField.getTag() + ", ProtobufInputStream.WIRETYPE_GROUP_START, baos);\n");
							result.append("baos.write(curMessageData);\n");
							result.append("ProtobufOutputStream.writeTag(" + curField.getTag() + ", ProtobufInputStream.WIRETYPE_GROUP_END, baos);\n");
						} else {
							result.append("ProtobufOutputStream.writeMessageTag(" + curField.getTag() + ", baos);\n");
							result.append("ProtobufOutputStream.writeRawVarint32(curMessageData.length, baos);\n");
							result.append("baos.write(curMessageData);\n");
						}
						result.append("}\n");
						result.append(curField.getName() + "Buffer = baos.toByteArray();\n");
					} else {
						if (curField.isGroup()) {
							result.append("java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();\n");
							result.append("ProtobufOutputStream.writeTag(" + curField.getTag() + ", ProtobufInputStream.WIRETYPE_GROUP_START, baos);\n");
							result.append("baos.write(" + curField.getFullyClarifiedJavaType() + "Serializer.serialize(message.get" + curField.getBeanName() + "()));\n");
							result.append("ProtobufOutputStream.writeTag(" + curField.getTag() + ", ProtobufInputStream.WIRETYPE_GROUP_END, baos);\n");
							result.append(curField.getName() + "Buffer = baos.toByteArray();\n");
						} else {
							result.append(curField.getName() + "Buffer = " + curField.getFullyClarifiedJavaType() + "Serializer.serialize(message.get" + curField.getBeanName() + "());\n");
							result.append("totalSize += ProtobufOutputStream.computeTagSize(" + curField.getTag() + ");\n");
							result.append("totalSize += ProtobufOutputStream.computeRawVarint32Size(" + curField.getName() + "Buffer.length);\n");
						}
					}
					result.append("totalSize += " + curField.getName() + "Buffer.length;\n");
					result.append("}\n");
					continue;
				}
				if (curField.getType().equals("string")) {
					result.append("byte[] " + curField.getName() + "Buffer = null;\n");
					result.append("if (message.has" + curField.getBeanName() + "()) {\n");
					if (curField.getNature().equals("repeated")) {
						result.append("java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();\n");
						result.append("for( int i=0;i<message.get" + curField.getBeanName() + "().size();i++) {\n");
						result.append("ProtobufOutputStream.writeString(" + curField.getTag() + ", message.get" + curField.getBeanName() + "().get(i), baos);\n");
						result.append("}\n");
						result.append(curField.getName() + "Buffer = baos.toByteArray();\n");
						result.append("totalSize += " + curField.getName() + "Buffer.length;\n");
					} else {
						result.append(curField.getName() + "Buffer = message.get" + curField.getBeanName() + "().getBytes(\"UTF-8\");\n");
						result.append("totalSize += " + curField.getName() + "Buffer.length;\n");
						result.append("totalSize += ProtobufOutputStream.computeTagSize(" + curField.getTag() + ");\n");
						result.append("totalSize += ProtobufOutputStream.computeRawVarint32Size(" + curField.getName() + "Buffer.length);\n");
					}
					result.append("}\n");
					continue;
				}

				if (curField.getNature().equals("repeated")) {
					result.append("if (message.has" + curField.getBeanName() + "()) {\n");
					if (curField.getType().equals("bytes")) {
						result.append("totalSize += ProtobufOutputStream.computeBytesSize(" + curField.getTag() + ", message.get" + curField.getBeanName() + "());\n");
					} else {
						result.append("for(int i=0;i<message.get" + curField.getBeanName() + "().size();i++) {\n");
						result.append("totalSize += ProtobufOutputStream.compute" + curField.getStreamBeanType() + "Size(" + curField.getTag() + ", message.get" + curField.getBeanName() + "().get(i));\n");
						result.append("}\n");
					}
					result.append("}\n");
				} else {
					result.append("if (message.has" + curField.getBeanName() + "()) {\n");
					result.append("totalSize += ");
					if (curField.getType().equals("bytes")) {
						result.append("message.get" + curField.getBeanName() + "().length;\n");
						result.append("totalSize += ProtobufOutputStream.computeTagSize(" + curField.getTag() + ");\n");
						result.append("totalSize += ProtobufOutputStream.computeRawVarint32Size(message.get" + curField.getBeanName() + "().length);\n");
					} else {
						result.append("ProtobufOutputStream.compute" + curField.getStreamBeanType() + "Size(" + curField.getTag() + ", message.get" + curField.getBeanName() + "());\n");
					}
					result.append("}\n");
				}
			}
			result.append("final byte[] result = new byte[totalSize];\nint position = 0;\n");
			for (ProtobufField curField : curMessage.getFields()) {
				result.append("if (message.has" + curField.getBeanName() + "()) {\n");
				if (curField.getType().equals("string")) {
					if (curField.getNature().equals("repeated")) {
						result.append("position = ProtobufOutputStream.writeRawBytes(" + curField.getName() + "Buffer, result, position);\n");
					} else {
						result.append("position = ProtobufOutputStream.writeString(" + curField.getTag() + "," + curField.getName() + "Buffer, result, position);\n");
					}
					result.append("}\n");
					continue;
				}

				if (curField.isEnumType()) {
					if (curField.getNature().equals("repeated")) {
						result.append("position = ProtobufOutputStream.writeRawBytes(" + curField.getName() + "Buffer, result, position);\n");
					} else {
						result.append("position = ProtobufOutputStream.writeEnum(" + curField.getTag() + ", message.get" + curField.getBeanName() + "().getValue(), result, position);\n");
					}
					result.append("}\n");
					continue;
				}

				if (curField.getNature().equals("repeated")) {
					if (curField.isComplexType()) {
						result.append("position = ProtobufOutputStream.writeRawBytes(" + curField.getName() + "Buffer, result, position);\n");
					} else {
						result.append("position = ProtobufOutputStream.writeRepeated" + curField.getStreamBeanType() + "(" + curField.getTag() + ", message.get" + curField.getBeanName() + "(), result, position);\n");
					}
				} else if (curField.isGroup()) {
					result.append("position = ProtobufOutputStream.writeRawBytes(" + curField.getName() + "Buffer, result, position);\n");
				} else {
					result.append("position = ProtobufOutputStream.write");
					if (!curField.isComplexType()) {
						result.append(curField.getStreamBeanType());
						result.append("(");
						result.append(curField.getTag());
						result.append(", message.get");
						result.append(curField.getBeanName());
						result.append("(), result, position);\n");
					} else {
						result.append("Bytes(" + curField.getTag() + ", " + curField.getName() + "Buffer, result, position);\n");
					}
				}
				result.append("}\n");
			}
			result.append("ProtobufOutputStream.checkNoSpaceLeft(result, position);\n");
			result.append("return result;\n");
			result.append("} catch (Exception e) {\n");
			result.append("throw new RuntimeException(e);\n");
			result.append("}\n");
		}
		result.append("}\n");
	}

	private static void createAssertInitialized(ProtobufMessage curMessage, StringBuilder result, String fullMessageType) {
		result.append("private static void assertInitialized(");
		result.append(fullMessageType);
		result.append(" message) {\n");
		for (ProtobufField curField : curMessage.getFields()) {
			if (curField.getNature().equals("required")) {
				result.append("if( !message.has" + curField.getBeanName() + "()) {\n");
				result.append("throw new IllegalArgumentException(\"Required field not initialized: ");
				result.append(curField.getName());
				result.append("\");\n}\n");
			}
		}
		result.append("}\n");
	}

	private static void createSerializeToStream(ProtobufMessage curMessage, StringBuilder result, String fullMessageType) {
		result.append("public static void serialize(" + fullMessageType + " message, java.io.OutputStream os) {\n");
		if (curMessage.getFields().isEmpty()) {
			result.append("return; \n");
		} else {
			result.append("try {\n");
			if (hasRequired(curMessage)) {
				result.append("assertInitialized(message);\n");
			}

			for (ProtobufField curField : curMessage.getFields()) {
				result.append("if (message.has" + curField.getBeanName() + "()) {\n");
				if (curField.getType().equals("string")) {
					if (curField.getNature().equals("repeated")) {
						result.append("for( int i=0;i<message.get" + curField.getBeanName() + "().size();i++) {\n");
						result.append("ProtobufOutputStream.writeString(" + curField.getTag() + ", message.get" + curField.getBeanName() + "().get(i), os);\n");
						result.append("}\n");
					} else {
						result.append("ProtobufOutputStream.writeString(" + curField.getTag() + ", message.get" + curField.getBeanName() + "(), os);\n");
					}
				} else if (curField.isEnumType()) {
					if (curField.getNature().equals("repeated")) {
						result.append("for( int i=0;i<message.get" + curField.getBeanName() + "().size();i++) {\n");
						result.append("ProtobufOutputStream.writeEnum(" + curField.getTag() + ", message.get" + curField.getBeanName() + "().get(i).getValue(), os);\n");
						result.append("}\n");
					} else {
						result.append("ProtobufOutputStream.writeEnum(" + curField.getTag() + ", message.get" + curField.getBeanName() + "().getValue(), os);\n");
					}
				} else if (curField.isGroup()) {
					if (curField.getNature().equals("repeated")) {
						result.append("for( int i=0;i<message.get" + curField.getBeanName() + "().size();i++) {\n");
						result.append("ProtobufOutputStream.writeTag(" + curField.getTag() + ", ProtobufInputStream.WIRETYPE_GROUP_START, os);\n");
						result.append("os.write(" + curField.getFullyClarifiedJavaType() + "Serializer.serialize(message.get" + curField.getBeanName() + "().get(i)));\n");
						result.append("ProtobufOutputStream.writeTag(" + curField.getTag() + ", ProtobufInputStream.WIRETYPE_GROUP_END, os);\n");
						result.append("}\n");
					} else {
						result.append("ProtobufOutputStream.writeTag(" + curField.getTag() + ", ProtobufInputStream.WIRETYPE_GROUP_START, os);\n");
						result.append("os.write(" + curField.getFullyClarifiedJavaType() + "Serializer.serialize(message.get" + curField.getBeanName() + "()));\n");
						result.append("ProtobufOutputStream.writeTag(" + curField.getTag() + ", ProtobufInputStream.WIRETYPE_GROUP_END, os);\n");
					}
				} else if (curField.isComplexType()) {
					if (curField.getNature().equals("repeated")) {
						result.append("for( int i=0;i<message.get" + curField.getBeanName() + "().size();i++) {\n");
						result.append("byte[] curMessageData = " + curField.getFullyClarifiedJavaType() + "Serializer.serialize(message.get" + curField.getBeanName() + "().get(i));\n");
						result.append("ProtobufOutputStream.writeMessageTag(" + curField.getTag() + ", os);\n");
						result.append("ProtobufOutputStream.writeRawVarint32(curMessageData.length, os);\n");
						result.append("os.write(curMessageData);\n");
						result.append("}\n");
					} else {
						result.append("byte[] curMessageData = " + curField.getFullyClarifiedJavaType() + "Serializer.serialize(message.get" + curField.getBeanName() + "());\n");
						result.append("ProtobufOutputStream.writeMessageTag(" + curField.getTag() + ", os);\n");
						result.append("ProtobufOutputStream.writeRawVarint32(curMessageData.length, os);\n");
						result.append("os.write(curMessageData);\n");
					}
				} else if (curField.getType().equals("bytes")) {
					result.append("ProtobufOutputStream.writeBytes(");
					result.append(curField.getTag());
					result.append(", message.get");
					result.append(curField.getBeanName());
					result.append("(), os);\n");
				} else {
					if (curField.getNature().equals("repeated")) {
						result.append("for( int i=0;i<message.get" + curField.getBeanName() + "().size();i++) {\n");
						result.append("ProtobufOutputStream.write");
						result.append(curField.getStreamBeanType());
						result.append("(");
						result.append(curField.getTag());
						result.append(", message.get");
						result.append(curField.getBeanName());
						result.append("().get(i), os);\n");
						result.append("}\n");
					} else {
						result.append("ProtobufOutputStream.write");
						result.append(curField.getStreamBeanType());
						result.append("(");
						result.append(curField.getTag());
						result.append(", message.get");
						result.append(curField.getBeanName());
						result.append("(), os);\n");
					}
				}
				result.append("}\n");
			}
			result.append("} catch (java.io.IOException e) {\n");
			result.append("throw new RuntimeException(\"Serializing to a byte array threw an IOException (should never happen).\", e);\n");
			result.append("}\n");
		}
		result.append("}\n");
	}

	private static void createParseFromBytesWithLimit(boolean interfaceBased, StringBuilder result, String fullMessageType) {
		if (interfaceBased) {
			result.append("public static " + fullMessageType + " parseFrom(MessageFactory factory, byte[] data, int offset, int length) throws java.io.IOException {\n");
		} else {
			result.append("public static " + fullMessageType + " parseFrom(byte[] data, int offset, int length) throws java.io.IOException {\n");
		}
		result.append("CurrentCursor cursor = new CurrentCursor();\n");
		result.append("cursor.addToPosition(offset);\n");
		result.append("cursor.setProcessUpToPosition(offset + length);\n");
		if (interfaceBased) {
			result.append("return parseFrom(factory, data, cursor);\n");
		} else {
			result.append("return parseFrom(data, cursor);\n");
		}
		result.append("}\n");
	}

	private static void createParseFromBytes(boolean interfaceBased, StringBuilder result, String fullMessageType) {
		if (interfaceBased) {
			result.append("public static " + fullMessageType + " parseFrom(MessageFactory factory, byte[] data) throws java.io.IOException {\n");
		} else {
			result.append("public static " + fullMessageType + " parseFrom(byte[] data) throws java.io.IOException {\n");
		}
		result.append("CurrentCursor cursor = new CurrentCursor();\n");
		if (interfaceBased) {
			result.append("return parseFrom(factory, data, cursor);\n");
		} else {
			result.append("return parseFrom(data, cursor);\n");
		}
		result.append("}\n");
	}

	private static void createParseFromBytesCursor(ProtobufMessage curMessage, boolean interfaceBased, StringBuilder result, String fullMessageType) {
		String factory = "";
		if (interfaceBased) {
			result.append("public static " + fullMessageType + " parseFrom(MessageFactory factory, byte[] data, CurrentCursor cursor) throws java.io.IOException {\n");
			result.append(fullMessageType + " message = (" + curMessage.getFullyClarifiedJavaName() + ")factory.create(\"" + curMessage.getFullyClarifiedJavaName() + "\");\n");
			result.append("if( message == null ) { \n");
			result.append("throw new java.io.IOException(\"Factory create invalid message for type: " + curMessage.getFullyClarifiedJavaName() + "\");\n");
			result.append("}\n");
			factory = "factory, ";
		} else {
			result.append("public static " + fullMessageType + " parseFrom(byte[] data, CurrentCursor cursor) throws java.io.IOException {\n");
			result.append(fullMessageType + " message = new " + fullMessageType + "();\n");
		}
		result.append("while(true) {\n");
		result.append("if (ProtobufInputStream.isAtEnd(data, cursor)) {\n");
		result.append("return message;\n");
		result.append("}\n");
		result.append("int varint = ProtobufInputStream.readRawVarint32(data, cursor);\n");
		result.append("int tag = ProtobufInputStream.getTagFieldNumber(varint);\n");
		if (curMessage.isGroup()) {
			result.append("int wireType = varint & ProtobufInputStream.TAG_TYPE_MASK;\n");
			result.append("if (wireType == ProtobufInputStream.WIRETYPE_GROUP_END) {\n");
			result.append("return message;\n");
			result.append("}\n");
		}
		result.append("switch(tag) {\n");
		result.append("case 0: \n");
		result.append("return message;\n ");
		result.append("default: \n ProtobufInputStream.skipUnknown(varint, data, cursor);\n break;\n");
		for (ProtobufField curField : curMessage.getFields()) {
			result.append("case " + curField.getTag() + ": \n");
			if (curField.isEnumType()) {
				if (curField.getNature().equals("repeated")) {
					result.append("if( message.get" + curField.getBeanName() + "() == null || message.get" + curField.getBeanName() + "().isEmpty()) {\n");
					result.append("message.set" + curField.getBeanName() + "(new java.util.ArrayList<" + curField.getFullyClarifiedJavaType() + ">());\n");
					result.append("}\n");
					result.append("message.get" + curField.getBeanName() + "().add(" + curField.getFullyClarifiedJavaType() + ".valueOf(ProtobufInputStream.readEnum(data,cursor)));\n");
				} else {
					result.append("message.set" + curField.getBeanName() + "(" + curField.getFullyClarifiedJavaType() + ".valueOf(ProtobufInputStream.readEnum(data,cursor)));\n");
				}
			} else if (curField.isComplexType()) {
				if (curField.isGroup()) {
					if (curField.getNature().equals("repeated")) {
						result.append("if( message.get" + curField.getBeanName() + "() == null || message.get" + curField.getBeanName() + "().isEmpty()) {\n");
						result.append("message.set" + curField.getBeanName() + "(new java.util.ArrayList<" + curField.getFullyClarifiedJavaType() + ">());\n");
						result.append("}\n");
						result.append(curField.getFullyClarifiedJavaType() + " temp" + curField.getBeanName() + " = " + curField.getFullyClarifiedJavaType() + "Serializer.parseFrom(" + factory + "data, cursor);\n");
						result.append("message.get" + curField.getBeanName() + "().add(temp" + curField.getBeanName() + ");\n");
					} else {
						result.append(curField.getFullyClarifiedJavaType() + " temp" + curField.getBeanName() + " = " + curField.getFullyClarifiedJavaType() + "Serializer.parseFrom(" + factory + "data, cursor);\n");
						result.append("message.set" + curField.getBeanName() + "(temp" + curField.getBeanName() + ");\n");
					}
				} else if (curField.getNature().equals("repeated")) {
					result.append("if( message.get" + curField.getBeanName() + "() == null || message.get" + curField.getBeanName() + "().isEmpty()) {\n");
					result.append("message.set" + curField.getBeanName() + "(new java.util.ArrayList<" + curField.getFullyClarifiedJavaType() + ">());\n");
					result.append("}\n");
					result.append("int length" + curField.getBeanName() + " = ProtobufInputStream.readRawVarint32(data,cursor);\n");
					result.append("message.get" + curField.getBeanName() + "().add(" + curField.getFullyClarifiedJavaType() + "Serializer.parseFrom(" + factory + "data, cursor.getCurrentPosition(), length" + curField.getBeanName() + "));\n");
					result.append("cursor.addToPosition(length" + curField.getBeanName() + ");\n");
				} else {
					result.append("int length" + curField.getBeanName() + " = ProtobufInputStream.readRawVarint32(data,cursor);\n");
					result.append("message.set" + curField.getBeanName() + "(" + curField.getFullyClarifiedJavaType() + "Serializer.parseFrom(" + factory + "data, cursor.getCurrentPosition(), length" + curField.getBeanName() + "));\n");
					result.append("cursor.addToPosition(length" + curField.getBeanName() + ");\n");
				}
			} else if (curField.getType().equals("bytes")) {
				result.append("message.set" + curField.getBeanName() + "(ProtobufInputStream.readBytes(data,cursor));\n");
			} else {
				if (curField.getNature().equals("repeated")) {
					result.append("if( message.get" + curField.getBeanName() + "() == null || message.get" + curField.getBeanName() + "().isEmpty()) {\n");
					result.append("message.set" + curField.getBeanName() + "(new java.util.ArrayList<" + curField.getFullyClarifiedJavaType() + ">());\n");
					result.append("}\n");
					result.append("message.get" + curField.getBeanName() + "().add(ProtobufInputStream.read" + curField.getStreamBeanType() + "(data,cursor));\n");
				} else {
					result.append("message.set" + curField.getBeanName() + "(ProtobufInputStream.read" + curField.getStreamBeanType() + "(data,cursor));\n");
				}
			}
			result.append("break;\n");
		}
		result.append("}\n");
		result.append("}\n");
		result.append("}\n");
	}

	private static void createParseFromStreamWithLength(boolean interfaceBased, StringBuilder result, String fullMessageType) {
		// streamed read
		if (interfaceBased) {
			result.append("public static " + fullMessageType + " parseFrom(MessageFactory factory, java.io.InputStream is, int offset, int length) throws java.io.IOException {\n");
		} else {
			result.append("public static " + fullMessageType + " parseFrom(java.io.InputStream is, int offset, int length) throws java.io.IOException {\n");
		}
		result.append("CurrentCursor cursor = new CurrentCursor();\n");
		result.append("cursor.addToPosition(offset);\n");
		result.append("cursor.setProcessUpToPosition(offset + length);\n");
		if (interfaceBased) {
			result.append("return parseFrom(factory, is, cursor);\n");
		} else {
			result.append("return parseFrom(is, cursor);\n");
		}
		result.append("}\n");
	}

	private static void createParseFromStream(boolean interfaceBased, StringBuilder result, String fullMessageType) {
		// streamed read
		if (interfaceBased) {
			result.append("/** Beware! All subsequent messages in stream will be consumed until end of stream (default protobuf behaivour).\n  **/");
			result.append("public static " + fullMessageType + " parseFrom(MessageFactory factory, java.io.InputStream is) throws java.io.IOException {\n");
		} else {
			result.append("public static " + fullMessageType + " parseFrom(java.io.InputStream is) throws java.io.IOException {\n");
		}
		result.append("CurrentCursor cursor = new CurrentCursor();\n");
		if (interfaceBased) {
			result.append("return parseFrom(factory, is, cursor);\n");
		} else {
			result.append("return parseFrom(is, cursor);\n");
		}
		result.append("}\n");
	}

	private static void createParseFromStreamCursor(ProtobufMessage curMessage, boolean interfaceBased, StringBuilder result, String fullMessageType) {
		String factory = "";
		if (interfaceBased) {
			result.append("public static " + fullMessageType + " parseFrom(MessageFactory factory, java.io.InputStream is, CurrentCursor cursor) throws java.io.IOException {\n");
			result.append(fullMessageType + " message = (" + curMessage.getFullyClarifiedJavaName() + ")factory.create(\"" + curMessage.getFullyClarifiedJavaName() + "\");\n");
			result.append("if( message == null ) { \n");
			result.append("throw new java.io.IOException(\"Factory create invalid message for type: " + curMessage.getFullyClarifiedJavaName() + "\");\n");
			result.append("}\n");
			factory = "factory, ";
		} else {
			result.append("public static " + fullMessageType + " parseFrom(java.io.InputStream is, CurrentCursor cursor) throws java.io.IOException {\n");
			result.append(fullMessageType + " message = new " + fullMessageType + "();\n");
		}
		result.append("while(true) {\n");
		result.append("if( cursor.getCurrentPosition() == cursor.getProcessUpToPosition() ) {\n");
		result.append("return message;\n");
		result.append("}\n");
		result.append("int varint = ProtobufInputStream.readRawVarint32(is, cursor);\n");
		result.append("int tag = ProtobufInputStream.getTagFieldNumber(varint);\n");
		result.append("if (ProtobufInputStream.isAtEnd(cursor)) {\n");
		result.append("return message;\n");
		result.append("}\n");
		if (curMessage.isGroup()) {
			result.append("int wireType = varint & ProtobufInputStream.TAG_TYPE_MASK;\n");
			result.append("if (wireType == ProtobufInputStream.WIRETYPE_GROUP_END) {\n");
			result.append("return message;\n");
			result.append("}\n");
		}
		result.append("switch(tag) {\n");
		result.append("case 0: \n");
		result.append("return message;\n ");
		result.append("default: \n ProtobufInputStream.skipUnknown(varint, is, cursor);\n break;");
		for (ProtobufField curField : curMessage.getFields()) {
			result.append("case " + curField.getTag() + ": \n");
			if (curField.isEnumType()) {
				if (curField.getNature().equals("repeated")) {
					result.append("if( message.get" + curField.getBeanName() + "() == null || message.get" + curField.getBeanName() + "().isEmpty()) {\n");
					result.append("message.set" + curField.getBeanName() + "(new java.util.ArrayList<" + curField.getFullyClarifiedJavaType() + ">());\n");
					result.append("}\n");
					result.append("message.get" + curField.getBeanName() + "().add(" + curField.getFullyClarifiedJavaType() + ".valueOf(ProtobufInputStream.readEnum(is,cursor)));\n");
				} else {
					result.append("message.set" + curField.getBeanName() + "(" + curField.getFullyClarifiedJavaType() + ".valueOf(ProtobufInputStream.readEnum(is,cursor)));\n");
				}
			} else if (curField.isGroup()) {
				if (curField.getNature().equals("repeated")) {
					result.append("if( message.get" + curField.getBeanName() + "() == null || message.get" + curField.getBeanName() + "().isEmpty()) {\n");
					result.append("message.set" + curField.getBeanName() + "(new java.util.ArrayList<" + curField.getFullyClarifiedJavaType() + ">());\n");
					result.append("}\n");
					result.append(curField.getFullyClarifiedJavaType() + " temp" + curField.getBeanName() + " = " + curField.getFullyClarifiedJavaType() + "Serializer.parseFrom(" + factory + "is, cursor);\n");
					result.append("message.get" + curField.getBeanName() + "().add(temp" + curField.getBeanName() + ");\n");
				} else {
					result.append(curField.getFullyClarifiedJavaType() + " temp" + curField.getBeanName() + " = " + curField.getFullyClarifiedJavaType() + "Serializer.parseFrom(" + factory + "is, cursor);\n");
					result.append("message.set" + curField.getBeanName() + "(temp" + curField.getBeanName() + ");\n");
				}
			} else if (curField.isComplexType()) {
				if (curField.getNature().equals("repeated")) {
					result.append("if( message.get" + curField.getBeanName() + "() == null || message.get" + curField.getBeanName() + "().isEmpty()) {\n");
					result.append("message.set" + curField.getBeanName() + "(new java.util.ArrayList<" + curField.getFullyClarifiedJavaType() + ">());\n");
					result.append("}\n");
					result.append("int length" + curField.getBeanName() + " = ProtobufInputStream.readRawVarint32(is,cursor);\n");
					result.append("message.get" + curField.getBeanName() + "().add(" + curField.getFullyClarifiedJavaType() + "Serializer.parseFrom(" + factory + "is, cursor.getCurrentPosition(), length" + curField.getBeanName() + "));\n");
				} else {
					result.append("int length" + curField.getBeanName() + " = ProtobufInputStream.readRawVarint32(is,cursor);\n");
					result.append("message.set" + curField.getBeanName() + "(" + curField.getFullyClarifiedJavaType() + "Serializer.parseFrom(" + factory + "is, cursor.getCurrentPosition(), length" + curField.getBeanName() + "));\n");
				}
				result.append("cursor.addToPosition(length" + curField.getBeanName() + ");\n");
			} else if (curField.getType().equals("bytes")) {
				result.append("message.set" + curField.getBeanName() + "(ProtobufInputStream.readBytes(is,cursor));\n");
			} else {
				if (curField.getNature().equals("repeated")) {
					result.append("if( message.get" + curField.getBeanName() + "() == null || message.get" + curField.getBeanName() + "().isEmpty()) {\n");
					result.append("message.set" + curField.getBeanName() + "(new java.util.ArrayList<" + curField.getFullyClarifiedJavaType() + ">());\n");
					result.append("}\n");
					result.append("message.get" + curField.getBeanName() + "().add(ProtobufInputStream.read" + curField.getStreamBeanType() + "(is,cursor));\n");
				} else {
					result.append("message.set" + curField.getBeanName() + "(ProtobufInputStream.read" + curField.getStreamBeanType() + "(is,cursor));\n");
				}
			}
			result.append("break;\n");
		}
		result.append("}\n");
		result.append("}\n");
		result.append("}\n");
	}

	private static void generateDefaultMessageImpl(ProtobufMessage curMessage, File output, String packageName) throws Exception {
		StringBuilder result = new StringBuilder();
		result.append("public class " + curMessage.getName() + "Impl implements " + curMessage.getFullyClarifiedJavaName() + " {\n");
		for (ProtobufField curField : curMessage.getFields()) {
			String javaType = constructType(curField);
			result.append("private " + javaType + " " + curField.getBeanName() + ";\n");
			result.append("private boolean has" + curField.getBeanName() + ";\n");
			result.append("public boolean has" + curField.getBeanName() + "() {\n");
			result.append("return has" + curField.getBeanName() + ";\n");
			result.append("}\n");
			result.append("public " + javaType + " get" + curField.getBeanName() + "() {\n");
			result.append("return " + curField.getBeanName() + ";\n");
			result.append("}\n");
			result.append("public void set" + curField.getBeanName() + "(" + javaType + " " + curField.getBeanName() + ") {\n");
			result.append("this." + curField.getBeanName() + " = " + curField.getBeanName() + ";\n");
			result.append("this.has" + curField.getBeanName() + " = true;\n");
			result.append("}\n");
		}
		result.append("}\n");

		File outputFile = new File(output, curMessage.getName() + "Impl.java");
		BufferedWriter messageWriter = new BufferedWriter(new FileWriter(outputFile));
		appendPackage(messageWriter, packageName);
		messageWriter.append(result.toString());
		messageWriter.flush();
		messageWriter.close();

		if (curMessage.getNestedMessages() != null) {
			for (ProtobufMessage message : curMessage.getNestedMessages()) {
				generateDefaultMessageImpl(message, output, packageName);
			}
		}

	}

	private static String generateMessage(ProtobufMessage curMessage, String outerClassName, GeneratorConfiguration config) {
		String chainingReturn = "void";
		if (config.isGenerateChaining()) {
			chainingReturn = curMessage.getName();
		}

		StringBuilder result = new StringBuilder();
		if (config.isInterfaceBased()) {
			result.append("public interface ");
			result.append(curMessage.getName());
			result.append(" {\n");
			for (ProtobufField curField : curMessage.getFields()) {
				if (curField.isDeprecated()) {
					result.append("@Deprecated\n");
				}
				result.append("boolean has");
				result.append(curField.getBeanName());
				result.append("();\n");
				String javaType = constructType(curField);
				if (curField.isDeprecated()) {
					result.append("@Deprecated\n");
				}
				result.append(javaType);
				result.append(" get");
				result.append(curField.getBeanName());
				result.append("();\n");
				if (curField.isDeprecated()) {
					result.append("@Deprecated\n");
				}
				result.append(chainingReturn + " set");
				result.append(curField.getBeanName());
				result.append("(");
				result.append(javaType);
				result.append(" ");
				result.append(curField.getName());
				result.append(");\n");
				if (config.isGenerateListHelpers() && curField.isListType()) {
					result.append(curField.getFullyClarifiedJavaType() + " get" + curField.getBeanName() + "(int index);\n");
					result.append("int get" + curField.getBeanName() + "Count();\n");
					result.append(chainingReturn + " set" + curField.getBeanName() + "(int index, " + curField.getFullyClarifiedJavaType() + " value);\n");

					result.append(chainingReturn + " add" + curField.getBeanName() + "(" + curField.getFullyClarifiedJavaType() + " value);\n");
					result.append(chainingReturn + " addAll" + curField.getBeanName() + "(java.lang.Iterable<? extends " + curField.getFullyClarifiedJavaType() + "> values);\n");
					result.append(chainingReturn + " clear" + curField.getBeanName() + "();\n");
				}
			}
		} else {
			String staticKeyword = "";
			if (outerClassName != null) {
				staticKeyword = "static";
			}
			result.append("public " + staticKeyword + " class " + curMessage.getName());
			if (config.getMessageExtendsClass() != null) {
				result.append(" extends " + config.getMessageExtendsClass());
			}
			result.append(" {\n");
			for (ProtobufField curField : curMessage.getFields()) {
				String javaType = constructType(curField);
				result.append("private " + javaType + " " + curField.getJavaFieldName() + ";\n");
				if (config.isGenerateStaticFields()) {
					result.append("public static final int " + curField.getName().toUpperCase(Locale.UK) + "_FIELD_NUMBER = " + curField.getTag() + ";\n");
				}
				result.append("private boolean has" + curField.getBeanName() + ";\n");
				result.append("public boolean has" + curField.getBeanName() + "() {\n");
				result.append("return has" + curField.getBeanName() + ";\n");
				result.append("}\n");
				result.append("public " + javaType + " get" + curField.getBeanName() + "() {\n");
				result.append("return " + curField.getJavaFieldName() + ";\n");
				result.append("}\n");
				result.append("public " + chainingReturn + " set" + curField.getBeanName() + "(" + javaType + " " + curField.getBeanName() + ") {\n");
				result.append("this." + curField.getJavaFieldName() + " = " + curField.getBeanName() + ";\n");
				result.append("this.has" + curField.getBeanName() + " = true;\n");
				if (config.isGenerateChaining()) {
					result.append("return this;\n");
				}
				result.append("}\n");
				if (config.isGenerateListHelpers() && curField.isListType()) {
					result.append("public " + curField.getFullyClarifiedJavaType() + " get" + curField.getBeanName() + "(int index) {\n");
					result.append("return this." + curField.getJavaFieldName() + ".get(index);\n");
					result.append("}\n");

					result.append("public int get" + curField.getBeanName() + "Count() {\n");
					result.append("return this." + curField.getJavaFieldName() + ".size();\n");
					result.append("}\n");

					result.append("public " + chainingReturn + " set" + curField.getBeanName() + "(int index, " + curField.getFullyClarifiedJavaType() + " value) {\n");
					result.append("this." + curField.getJavaFieldName() + ".set(index, value);\n");
					if (config.isGenerateChaining()) {
						result.append("return this;\n");
					}
					result.append("}\n");

					result.append("public " + chainingReturn + " add" + curField.getBeanName() + "(" + curField.getFullyClarifiedJavaType() + " value) {\n");
					initRepeatedFieldIfEmpty(result, curField);
					result.append("this." + curField.getJavaFieldName() + ".add(value);\n");
					if (config.isGenerateChaining()) {
						result.append("return this;\n");
					}
					result.append("}\n");

					result.append("public " + chainingReturn + " addAll" + curField.getBeanName() + "(java.lang.Iterable<? extends " + curField.getFullyClarifiedJavaType() + "> values) {\n");
					initRepeatedFieldIfEmpty(result, curField);
					result.append("if (values instanceof java.util.Collection) {\n");
					result.append("@SuppressWarnings(\"unsafe\") final\n");
					result.append("java.util.Collection<? extends " + curField.getFullyClarifiedJavaType() + "> collection = (java.util.Collection<? extends " + curField.getFullyClarifiedJavaType() + ">) values;\n");
					result.append("this." + curField.getJavaFieldName() + ".addAll(collection);\n");
					result.append("} else {\n");
					result.append("for (final " + curField.getFullyClarifiedJavaType() + " value : values) {\n");
					result.append("this." + curField.getJavaFieldName() + ".add(value);\n");
					result.append("}\n}\n");
					if (config.isGenerateChaining()) {
						result.append("return this;\n");
					}
					result.append("}\n");

					result.append("public " + chainingReturn + " clear" + curField.getBeanName() + "() {\n");
					result.append("this.has" + curField.getBeanName() + " = false;\n");
					result.append("this." + curField.getJavaFieldName() + " = null;\n");
					if (config.isGenerateChaining()) {
						result.append("return this;\n");
					}
					result.append("}\n");
				}
			}
			if (config.isGenerateToString()) {
				result.append("@Override\n");
				result.append("public String toString() {\n");
				result.append("java.lang.StringBuilder builder = new java.lang.StringBuilder();\n");
				result.append("try {\n");
				result.append("toString(builder);\n");
				result.append("return builder.toString();\n");
				result.append("} catch (java.io.IOException e) {\n");
				result.append("throw new RuntimeException(\"Unable toString\", e);\n");
				result.append("}\n");
				result.append("}\n");
				result.append("public void toString(java.lang.Appendable a_) throws java.io.IOException {\n");
				result.append("a_.append(\"" + curMessage.getName() + " [\");\n");
				for (int i = 0; i < curMessage.getFields().size(); i++) {
					ProtobufField curField = curMessage.getFields().get(i);
					if (i != 0) {
						result.append("a_.append(\",\");\n");
					}
					if (curField.isComplexType()) {
						if (curField.isListType()) {
							result.append("a_.append(\" " + curField.getJavaFieldName() + "=\");\n");
							result.append("if (" + curField.getJavaFieldName() + " != null ) {\n");
							result.append("a_.append(\"[\");\n");
							result.append("for( int i=0;i<" + curField.getJavaFieldName() + ".size();i++ ) {\n");
							result.append(curField.getFullyClarifiedJavaType() + " cur = " + curField.getJavaFieldName() + ".get(i);\n");
							result.append("if( i != 0 ) {\n ");
							result.append("a_.append(\", \");\n");
							result.append("}\n");
							if( curField.isEnumType() ) {
								result.append("a_.append(cur.toString());\n");
							} else {
								result.append("cur.toString(a_);\n");
							}
							result.append("}\n");
							result.append("a_.append(\"]\");\n");
							result.append("} else {\n");
							result.append("a_.append(\"null\");\n");
							result.append("}\n");
						} else if (curField.isEnumType()) {
							result.append("a_.append(\" " + curField.getJavaFieldName() + "=\");\n");
							result.append("if ( " + curField.getJavaFieldName() + " != null ) {\n");
							result.append("a_.append(" + curField.getJavaFieldName() + ".toString());\n");
							result.append("} else {\n");
							result.append("a_.append(\"null\");\n");
							result.append("}\n");
						} else {
							result.append("a_.append(\" " + curField.getJavaFieldName() + "=\");\n");
							result.append("if ( " + curField.getJavaFieldName() + " != null ) {\n");
							result.append(curField.getJavaFieldName() + ".toString(a_);\n");
							result.append("} else {\n");
							result.append("a_.append(\"null\");\n");
							result.append("}\n");
						}
					} else {
						result.append("a_.append(\" " + curField.getJavaFieldName() + "=\" + " + curField.getJavaFieldName() + ");\n");
					}
				}
				result.append("a_.append(\"]\");\n");
				result.append("}\n");
			}
		}

		for (ProtobufMessage innerMessage : curMessage.getNestedMessages()) {
			result.append(generateMessage(innerMessage, outerClassName, config));
			String serializerData = generateSerializer(innerMessage, outerClassName, config);
			result.append(serializerData);
		}
		for (ProtobufEnum curEnum : curMessage.getEnums()) {
			result.append(generateEnum(curEnum));
		}
		result.append("}\n");
		return result.toString();
	}

	private static void initRepeatedFieldIfEmpty(StringBuilder result, ProtobufField curField) {
		result.append("if( this." + curField.getJavaFieldName() + " == null ) {\n");
		result.append("this." + curField.getJavaFieldName() + " = new java.util.ArrayList<" + curField.getFullyClarifiedJavaType() + ">();\n");
		result.append("this.has" + curField.getBeanName() + " = true;\n");
		result.append("}\n");
	}

	private static String generateEnum(ProtobufEnum pEnum) {
		StringBuilder result = new StringBuilder();
		result.append("public enum ");
		result.append(pEnum.getName());
		result.append(" {\n");
		Map<Long, EnumValue> added = new HashMap<Long, EnumValue>();
		List<EnumValue> duplicate = new ArrayList<EnumValue>();
		for (EnumValue curValue : pEnum.getValues()) {
			if (added.containsKey(curValue.getId())) {
				duplicate.add(curValue);
				continue;
			}
			result.append(curValue.getName());
			result.append("(");
			result.append(curValue.getId());
			result.append("),\n");
			added.put(curValue.getId(), curValue);
		}

		for (EnumValue curDuplicate : duplicate) {
			result.append(";\npublic static final ");
			result.append(pEnum.getName());
			result.append(" ");
			result.append(curDuplicate.getName());
			result.append(" = ");
			result.append(added.get(curDuplicate.getId()).getName());
		}

		result.append(";\npublic static ");
		result.append(pEnum.getName());
		result.append(" valueOf(int value) {\nswitch (value) {\n");
		for (EnumValue curValue : pEnum.getValues()) {
			if (duplicate.contains(curValue)) {
				continue;
			}
			result.append("case ");
			result.append(curValue.getId());
			result.append(": return ");
			result.append(curValue.getName());
			result.append(";\n");
		}
		result.append("default: return null;\n}\n}\nprivate ");
		result.append(pEnum.getName());
		result.append("(int value) {\nthis.value = value;\n}\nprivate int value;\npublic int getValue() {\nreturn value;\n}\n}\n\n");
		return result.toString();
	}

	private static void appendPackage(BufferedWriter w, String packageName) throws Exception {
		if (packageName != null) {
			w.append("package ");
			w.append(packageName);
			w.append(";\n\n");
		}
	}

	private static String constructType(ProtobufField curField) {
		StringBuilder result = new StringBuilder();
		String javaType = curField.getFullyClarifiedJavaType();
		if (curField.isListType()) {
			result.append("java.util.List<");
		}
		result.append(javaType);
		if (curField.isListType()) {
			result.append(">");
		}
		return result.toString();
	}

	private static File createPackage(File parent, String packageName) {
		String[] paths = packageName.split("\\.");
		File curDirectory = parent;
		for (String curPath : paths) {
			File curDirPath = new File(curDirectory, curPath);
			if (!curDirPath.exists() || !curDirPath.isDirectory()) {
				curDirPath.mkdir();
			}
			curDirectory = curDirPath;
		}
		return curDirectory;
	}

	private static boolean hasRequired(ProtobufMessage message) {
		for (ProtobufField curField : message.getFields()) {
			if (curField.getNature().equals("required")) {
				return true;
			}
		}
		return false;
	}

}
